<!-- 高亮文本输入框组件的主模板 -->
<template>
    <!-- 主容器：监听点击事件处理空白区域点击聚焦到最后一个块 -->
    <div class="highlight-input" ref="containerRef" @click="handleContainerClick" :class="{ 'is-empty': isInputEmpty }">
        <!--
        动态渲染任意数量的文本块和高亮块
        每个span元素代表一个可编辑的文本块，可以是：
        1. 普通文本块：显示普通文本，用户可以编辑
        2. 高亮块：显示高亮文本，有占位符功能
      -->
        <span v-for="(block, index) in blocks" :key="block.id" :class="[
            block.type === 'text' ? 'normal-text' : 'highlight-block',
            { 'has-content': block.type === 'highlight' && block.hasUserInput },
            { 'after-highlight': index > 0 && blocks[index - 1]?.type === 'highlight' }
        ]" contenteditable="true" :data-index="index"
            :data-placeholder="block.type === 'highlight' ? block.placeholder : undefined"
            @input="onBlockInput($event, index)" @focus="onBlockFocus(index)" @blur="onBlockBlur(index)"
            @keydown="handleKeyNavigation($event, index)" :ref="(el) => setBlockRef(index, el)" spellcheck="false">{{
                block.type === 'highlight' ? block.displayValue : block.value }}</span>

        <!-- 整体占位符：当输入框为空时显示 -->
        <div v-if="isInputEmpty" class="input-placeholder" @click="handlePlaceholderClick">
            {{ placeholder }}
        </div>
    </div>
</template>

<!-- Vue3 Composition API 脚本部分 -->
<script setup>
// 导入Vue3的响应式API和生命周期钩子
import { ref, onMounted, nextTick, watch, computed } from 'vue'

/**
 * Vue3 高亮文本输入框组件
 * 功能：支持高亮显示特定文本区域，可编辑文本内容
 * 特点：动态解析后端返回的文本，自动识别中括号内的高亮区域
 *
 * 主要特性：
 * 1. 支持任意数量的高亮区域
 * 2. 键盘导航（方向键、回车、删除）
 * 3. v-model双向绑定
 * 4. 中括号保留控制
 * 5. 可配置的回车换行行为
 * 6. 高亮区域复制功能（回车复制块到下一行）
 * 7. 整体占位符支持
 * 8. Ctrl/Shift+Enter快捷键
 * 9. 文本中间换行支持
 * 10. 高亮区域删除功能
 */

// ==================== 事件定义 ====================
// 定义组件可以发射的事件：提交、回车、内容变化、v-model更新
const emit = defineEmits(['submit', 'enter', 'change', 'update:modelValue'])

// ==================== Props定义 ====================
// 定义组件接收的属性
const props = defineProps({
    // v-model绑定的值（用户的实际数据）
    modelValue: {
        type: String,        // 字符串类型
        default: ''          // 默认为空字符串
    },
    // 组件内部的预设模板（包含中括号的模板文本）
    presetText: {
        type: String,        // 字符串类型
        default: ''          // 默认为空字符串
    },
    // 是否在最终输出中保留中括号
    keepBrackets: {
        type: Boolean,       // 布尔类型
        default: false       // 默认不保留中括号
    },
    // 是否允许回车换行
    allowLineBreak: {
        type: Boolean,       // 布尔类型
        default: false       // 默认不允许换行，保持原有行为
    },
    // 整个输入框的占位符文本（当没有任何内容时显示）
    placeholder: {
        type: String,        // 字符串类型
        default: '请输入内容...'  // 默认占位符文本
    },
    // Ctrl/Shift+Enter的行为：'line-break'表示换行，'submit'表示提交
    ctrlEnterAction: {
        type: String,        // 字符串类型
        default: 'line-break', // 默认是换行
        validator: (value) => ['line-break', 'submit'].includes(value)
    },
    // 是否启用高亮块复制功能（回车时复制高亮块到下一行）
    enableHighlightDuplication: {
        type: Boolean,       // 布尔类型
        default: true        // 默认启用高亮块复制功能
    }
})

// ==================== 响应式数据定义 ====================
/**
 * 动态块数组，存储所有文本块和高亮块的数据
 * 每个块包含以下属性：
 * - id: 唯一标识符，用于Vue的key追踪
 * - type: 块类型，'text'表示普通文本，'highlight'表示高亮区域
 * - value: 块的实际值（用户输入的内容）
 * - placeholder: 占位符文本（仅高亮块有效）
 * - displayValue: 显示值（可能是占位符或用户输入）
 * - hasUserInput: 是否有用户输入（仅高亮块有效）
 */
const blocks = ref([])

/**
 * DOM引用数组，存储每个块对应的DOM元素
 * 索引与blocks数组对应，用于：
 * - 光标定位和移动操作
 * - 焦点控制和切换
 * - 键盘导航功能
 */
const blockRefs = ref([])

/**
 * 主容器的DOM引用，用于全选等操作
 */
const containerRef = ref(null)

// ==================== 计算属性 ====================
/**
 * 判断输入框是否为空（用于显示整体placeholder）
 * 当没有任何blocks或所有blocks都为空时，认为输入框为空
 */
const isInputEmpty = computed(() => {
    if (blocks.value.length === 0) return true

    // 检查是否所有块都为空
    return blocks.value.every(block => {
        if (block.type === 'text') {
            return !block.value || block.value.trim() === ''
        } else {
            // 高亮块：没有用户输入或输入为空
            return !block.hasUserInput || !block.value || block.value.trim() === ''
        }
    })
})

// ==================== 工具函数 ====================
/**
 * 解析预设文本，生成动态块数组
 * @param {string} text - 预设文本
 * @returns {Array} 解析后的块数组
 */
function parsePresetText(text) {
    const blocks = []
    const regex = /\[([^\]]*)\]/g
    let lastIndex = 0
    let match
    let blockId = 0

    // 遍历所有匹配的中括号
    while ((match = regex.exec(text)) !== null) {
        // 添加中括号前的普通文本块（如果存在）
        if (match.index > lastIndex) {
            const textContent = text.substring(lastIndex, match.index)
            if (textContent) {
                blocks.push({
                    id: `block-${blockId++}`,
                    type: 'text',
                    value: textContent
                })
            }
        }

        // 添加高亮块
        blocks.push({
            id: `block-${blockId++}`,
            type: 'highlight',
            placeholder: match[1], // 中括号内的内容作为占位符
            value: '',             // 用户输入的内容
            displayValue: match[1], // 显示的内容（初始为占位符）
            hasUserInput: false    // 是否有用户输入
        })

        lastIndex = regex.lastIndex
    }

    // 添加最后一段普通文本（如果存在）
    if (lastIndex < text.length) {
        const textContent = text.substring(lastIndex)
        if (textContent) {
            blocks.push({
                id: `block-${blockId++}`,
                type: 'text',
                value: textContent
            })
        }
    }

    return blocks
}

/**
 * 设置光标到元素的指定位置
 * 用于精确控制光标在可编辑元素中的位置
 * @param {HTMLElement} el - 目标DOM元素
 * @param {string} pos - 位置：'start'表示开头，'end'表示结尾
 */
function setCursorToPos(el, pos = 'start') {
    if (!el) return                           // 如果元素不存在，直接返回

    try {
        const sel = window.getSelection()       // 获取当前的选择对象
        if (!sel) return                        // 如果选择对象不存在，直接返回

        sel.removeAllRanges()                   // 清除所有现有的选择范围

        const range = document.createRange()    // 创建一个新的Range对象

        if (pos === 'start') {
            // 设置到开头
            if (el.childNodes.length > 0) {
                const firstNode = el.childNodes[0]
                if (firstNode.nodeType === Node.TEXT_NODE) {
                    range.setStart(firstNode, 0)
                    range.setEnd(firstNode, 0)
                } else {
                    range.setStart(el, 0)
                    range.setEnd(el, 0)
                }
            } else {
                range.setStart(el, 0)
                range.setEnd(el, 0)
            }
        } else {
            // 设置到结尾
            if (el.childNodes.length > 0) {
                const lastNode = el.childNodes[el.childNodes.length - 1]
                if (lastNode.nodeType === Node.TEXT_NODE) {
                    const textLength = lastNode.textContent.length
                    range.setStart(lastNode, textLength)
                    range.setEnd(lastNode, textLength)
                } else {
                    range.setStart(el, el.childNodes.length)
                    range.setEnd(el, el.childNodes.length)
                }
            } else {
                const textLength = el.textContent.length
                range.setStart(el, textLength)
                range.setEnd(el, textLength)
            }
        }

        sel.addRange(range)                     // 添加新的选择范围，设置光标位置
    } catch (error) {
        // 静默处理错误，避免控制台噪音
    }
}

/**
 * 初始化时聚焦到第一个高亮区域
 * 仅在组件挂载时调用，确保光标在第一个高亮区域的最前面
 */
function focusFirstHighlight() {
    // 使用递归重试机制，确保DOM完全准备好
    const tryFocus = (attempts = 0) => {
        const maxAttempts = 10 // 最大重试次数

        // 在blocks数组中查找第一个高亮区域的索引
        const firstHighlightIndex = blocks.value.findIndex(block => block.type === 'highlight')

        if (firstHighlightIndex !== -1) {
            const block = blocks.value[firstHighlightIndex]
            const targetRef = blockRefs.value[firstHighlightIndex]

            // 检查DOM元素是否存在且在文档中
            if (targetRef && block && document.contains(targetRef)) {
                // 先清空显示值，显示空内容而不是占位符
                if (!block.hasUserInput) {
                    block.displayValue = ''
                }

                // 强制更新DOM
                nextTick(() => {
                    // 设置焦点
                    targetRef.focus()

                    // 立即设置光标位置
                    setTimeout(() => {
                        // 清空元素内容，确保光标在最前面
                        if (!block.hasUserInput) {
                            targetRef.textContent = ''
                        }

                        // 设置光标到最前面
                        const range = document.createRange()
                        const sel = window.getSelection()

                        if (sel) {
                            sel.removeAllRanges()
                            range.setStart(targetRef, 0)
                            range.collapse(true)
                            sel.addRange(range)
                        }
                    }, 10)
                })

                return // 成功设置，退出函数
            }
        }

        // 如果还没准备好且没有超过最大重试次数，继续重试
        if (attempts < maxAttempts) {
            setTimeout(() => tryFocus(attempts + 1), 200)
        }
    }

    // 延迟开始，确保组件完全挂载
    setTimeout(() => tryFocus(), 100)
}

/**
 * 设置光标到元素的最左侧（开头位置）
 * 这是setCursorToPos函数的简化版本，专门用于设置光标到开头
 * @param {HTMLElement} el - 目标DOM元素
 */
function setCursorToStart(el) {
    if (!el) return                           // 如果元素不存在，直接返回

    try {
        const range = document.createRange()    // 创建一个新的Range对象
        const sel = window.getSelection()      // 获取当前的选择对象

        if (!sel) return                       // 如果选择对象不存在，直接返回

        sel.removeAllRanges()                  // 清除所有现有的选择范围

        // 如果元素为空或只有空白字符，直接设置到元素开头
        if (el.childNodes.length === 0 || el.textContent.trim() === '') {
            range.setStart(el, 0)                // 设置范围开始位置
            range.setEnd(el, 0)                  // 设置范围结束位置
        } else {
            // 如果元素有内容，设置到第一个文本节点的开头
            const firstTextNode = el.childNodes[0]
            if (firstTextNode && firstTextNode.nodeType === Node.TEXT_NODE) {
                range.setStart(firstTextNode, 0)   // 设置到文本节点的开头
                range.setEnd(firstTextNode, 0)
            } else {
                range.setStart(el, 0)              // 回退到元素开头
                range.setEnd(el, 0)
            }
        }

        sel.addRange(range)                    // 添加新的选择范围
    } catch (error) {
        // 静默处理错误，避免控制台噪音
    }
}

// ==================== 动态事件处理 ====================
/**
 * 设置块的DOM引用
 * 将DOM元素存储到blockRefs数组中，用于后续的光标操作
 * @param {number} index - 块在数组中的索引位置
 * @param {HTMLElement} el - 对应的DOM元素
 */
function setBlockRef(index, el) {
    blockRefs.value[index] = el               // 将DOM元素引用存储到对应索引位置
}

/**
 * 块输入事件处理
 * 当用户在任何块中输入文本时触发，更新对应的数据状态
 * @param {Event} e - 输入事件对象
 * @param {number} index - 当前块的索引位置
 */
function onBlockInput(e, index) {
    // 获取输入的文本内容，移除换行符（因为我们不支持多行）
    const value = e.target.innerText.replace(/\n/g, '')
    const block = blocks.value[index]         // 获取对应的块数据

    // 检查块是否还存在（防止在删除操作后触发事件）
    if (!block) return

    if (block.type === 'text') {
        // 处理普通文本块：直接更新value
        block.value = value
    } else {
        // 处理高亮块：更新value、用户输入状态和显示值
        block.value = value                     // 存储用户输入的实际值
        block.hasUserInput = value.length > 0  // 标记是否有用户输入
        block.displayValue = value              // 更新显示值
    }

    emitChangeEvent()                         // 触发变化事件，更新v-model
}

/**
 * 块聚焦事件处理
 * 当用户点击或通过键盘导航到某个块时触发
 * @param {number} index - 当前聚焦块的索引位置
 */
function onBlockFocus(index) {
    const block = blocks.value[index]         // 获取对应的块数据

    // 检查块是否还存在（防止在删除操作后触发事件）
    if (!block) return

    if (block.type === 'highlight') {
        // 处理高亮块的聚焦逻辑
        // 如果没有用户输入，清空显示内容以便用户输入
        if (!block.hasUserInput) {
            block.displayValue = ''               // 清空显示值，隐藏占位符

            // 等待DOM更新后设置光标位置
            nextTick(() => {
                const el = blockRefs.value[index]   // 获取对应的DOM元素
                if (el) {
                    // 清空元素内容并设置光标到开头
                    el.textContent = ''

                    // 立即设置光标位置
                    const range = document.createRange()
                    const sel = window.getSelection()

                    if (sel) {
                        sel.removeAllRanges()
                        range.setStart(el, 0)
                        range.collapse(true)
                        sel.addRange(range)
                    }
                }
            })
        }
    } else {
        // 处理普通文本块的聚焦逻辑
        nextTick(() => {
            const el = blockRefs.value[index]     // 获取对应的DOM元素
            if (el) {
                // 确保光标可见，特别是在空文本块或文本开头时
                ensureCursorVisible(el)
            }
        })
    }
}

/**
 * 块失焦事件处理
 * 当用户离开某个块时触发，用于恢复占位符显示
 * @param {number} index - 失去焦点的块索引位置
 */
function onBlockBlur(index) {
    // 检查块是否还存在（防止删除后触发blur事件）
    const block = blocks.value[index]
    if (!block) return

    if (block.type === 'highlight' && !block.hasUserInput) {
        // 高亮块且没有用户输入时，恢复显示占位符
        block.displayValue = block.placeholder  // 恢复占位符文本显示
    }
}

// ==================== 键盘导航处理 ====================
/**
 * 处理键盘导航和特殊按键
 * 支持左右箭头键在块间切换，以及回车、删除等特殊按键的自定义处理
 * @param {KeyboardEvent} e - 键盘事件对象
 * @param {number} currentIndex - 当前聚焦块的索引位置
 */
function handleKeyNavigation(e, currentIndex) {
    // 检测按下的按键类型
    const isLeft = e.key === 'ArrowLeft'        // 左箭头键
    const isRight = e.key === 'ArrowRight'      // 右箭头键
    const isEnter = e.key === 'Enter'           // 回车键

    const isBackspace = e.key === 'Backspace'  // 退格键
    const isDelete = e.key === 'Delete'         // 删除键
    const isCtrlA = e.key === 'a' && (e.ctrlKey || e.metaKey)  // Ctrl+A全选
    const isCtrlC = e.key === 'c' && (e.ctrlKey || e.metaKey)  // Ctrl+C复制
    const isCtrlEnter = isEnter && (e.ctrlKey || e.metaKey) && !e.shiftKey    // 纯Ctrl+Enter
    const isShiftEnter = isEnter && e.shiftKey && !e.ctrlKey && !e.metaKey  // 纯Shift+Enter


    // 处理Ctrl+A全选 - 选择所有文本内容
    if (isCtrlA) {
        e.preventDefault()                        // 阻止默认的全选行为
        selectAllText()                           // 调用全选函数
        return
    }

    // 处理Ctrl+C复制 - 复制完整文本内容
    if (isCtrlC) {
        handleCopyText()                          // 调用复制处理函数
        return
    }

    // 处理Ctrl+Enter和Shift+Enter - 根据ctrlEnterAction属性和块类型决定行为
    if (isCtrlEnter || isShiftEnter) {


        if (props.ctrlEnterAction === 'line-break') {
            // 如果配置为换行行为，使用统一的换行处理函数
            e.preventDefault()                      // 阻止默认行为
            handleLineBreak(currentIndex, e.target) // 统一处理换行行为
            return
        } else {
            // 如果配置为提交，阻止默认行为并触发提交
            e.preventDefault()
            handleFormSubmit()                      // 触发表单提交
            return
        }
    }

    // 处理回车键 - 根据块类型和配置决定行为
    if (isEnter) {
        const currentBlock = blocks.value[currentIndex]

        if (currentBlock && currentBlock.type === 'highlight') {
            // 高亮区域的回车行为
            if (props.enableHighlightDuplication && props.allowLineBreak) {
                // 如果启用高亮块复制：使用统一的换行处理函数
                e.preventDefault()                    // 阻止默认的换行行为
                handleLineBreak(currentIndex, e.target) // 统一处理换行行为
                return
            } else if (props.allowLineBreak) {
                // 如果不启用复制但允许换行：允许块内换行
                return
            } else {
                // 如果不启用复制也不允许换行：跳转到下一个块
                e.preventDefault()                    // 阻止默认的换行行为
                handleEnterKey(currentIndex)          // 调用回车键处理函数
                return
            }
        } else if (props.allowLineBreak) {
            // 普通文本区域：如果允许换行，则允许默认的换行行为
            return
        } else {
            // 普通文本区域：如果不允许换行，跳转到下一个块
            e.preventDefault()                      // 阻止默认的换行行为
            handleEnterKey(currentIndex)            // 调用回车键处理函数
            return
        }
    }

    // 处理backspace键 - 在高亮区域有特殊的删除逻辑
    if (isBackspace) {
        handleBackspaceKey(e, currentIndex)       // 调用退格键处理函数
        return
    }

    // 处理delete键 - 在高亮区域有特殊的删除逻辑
    if (isDelete) {
        handleDeleteKey(e, currentIndex)          // 调用删除键处理函数
        return
    }

    // 处理左右箭头键导航 - 只有在光标到达边界时才切换块
    if (!isLeft && !isRight) return            // 如果不是箭头键，直接返回

    const el = e.target                         // 获取当前元素
    const sel = window.getSelection()           // 获取当前选择对象
    if (!sel || sel.rangeCount === 0) return   // 如果没有选择范围，直接返回

    // 改进的光标位置检测
    const range = sel.getRangeAt(0)             // 获取第一个选择范围
    const isAtStart = isCaretAtStart(el, range) // 使用更可靠的开头检测
    const isAtEnd = isCaretAtEnd(el, range)     // 使用更可靠的结尾检测

    if (isLeft && isAtStart && currentIndex > 0) {
        // 左箭头键且光标在开头且不是第一个块：切换到前一个块
        e.preventDefault()                      // 阻止默认的光标移动行为
        focusBlock(currentIndex - 1, 'end')     // 聚焦到前一个块的末尾
    } else if (isRight && isAtEnd && currentIndex < blocks.value.length - 1) {
        // 右箭头键且光标在结尾且不是最后一个块：切换到下一个块
        e.preventDefault()                      // 阻止默认的光标移动行为
        focusBlock(currentIndex + 1, 'start')   // 聚焦到下一个块的开头
    }
}

/**
 * 统一的换行处理函数
 * 根据当前块类型和配置决定换行行为
 * @param {number} currentIndex - 当前块的索引位置
 * @param {HTMLElement} targetElement - 目标DOM元素
 */
function handleLineBreak(currentIndex, targetElement) {
    const currentBlock = blocks.value[currentIndex]

    if (currentBlock && currentBlock.type === 'highlight' && props.enableHighlightDuplication) {
        // 高亮区域且启用复制：复制高亮块到下一行
        duplicateHighlightBlock(currentIndex)
    } else {
        // 普通文本区域或高亮区域未启用复制：插入换行符
        insertLineBreak(targetElement)
    }
}

/**
 * 复制高亮块到下一行
 * 在当前高亮块后插入换行符和相同的高亮块
 * @param {number} currentIndex - 当前高亮块的索引位置
 */
function duplicateHighlightBlock(currentIndex) {
    const currentBlock = blocks.value[currentIndex]
    if (!currentBlock || currentBlock.type !== 'highlight') return

    // 创建换行符文本块
    const lineBreakBlock = {
        id: `text_${Date.now()}_${Math.random()}`,
        type: 'text',
        value: '\n'
    }

    // 创建新的高亮块（复制当前块的结构，但清空内容）
    const newHighlightBlock = {
        id: `highlight_${Date.now()}_${Math.random()}`,
        type: 'highlight',
        placeholder: currentBlock.placeholder,
        value: '',                          // 新块开始时为空
        hasUserInput: false,                // 新块没有用户输入
        displayValue: currentBlock.placeholder  // 显示占位符
    }

    // 在当前块后插入换行符和新的高亮块
    blocks.value.splice(currentIndex + 1, 0, lineBreakBlock, newHighlightBlock)

    // 等待DOM更新后聚焦到新的高亮块
    nextTick(() => {
        // 新高亮块的索引是 currentIndex + 2（因为插入了换行符）
        const newBlockIndex = currentIndex + 2
        focusBlock(newBlockIndex, 'start')

        // 触发内容变化事件
        emitChangeEvent()
    })
}

/**
 * 处理回车键事件
 * 当allowLineBreak为false时，回车键用于在块之间跳转，到达最后一个块时触发提交
 * 当allowLineBreak为true时，此函数不会被调用，允许正常的换行行为
 * @param {number} currentIndex - 当前块的索引位置
 */
function handleEnterKey(currentIndex) {
    // 回车键的行为：跳转到下一个组件块
    if (currentIndex < blocks.value.length - 1) {
        // 如果不是最后一个块，跳到下一个组件块的开头
        focusBlock(currentIndex + 1, 'start')
    } else {
        // 如果是最后一个组件块，触发表单提交或其他自定义行为
        // 这里可以触发表单提交或其他自定义逻辑


        // 可以在这里添加自定义的回车处理逻辑
        // 例如：触发表单提交、显示提示信息等
        handleFormSubmit()                      // 调用表单提交处理函数
    }
}

/**
 * 处理backspace键事件
 * 支持高亮区域的渐进式删除：第一次清空内容，第二次删除整个区域
 * @param {KeyboardEvent} e - 键盘事件对象
 * @param {number} currentIndex - 当前块的索引位置
 */
function handleBackspaceKey(e, currentIndex) {
    const el = e.target                       // 获取当前DOM元素
    const sel = window.getSelection()         // 获取当前选择对象
    const block = blocks.value[currentIndex]  // 获取当前块数据

    // 如果是高亮区域，实现特殊的删除逻辑
    if (block.type === 'highlight') {
        // 检查是否在高亮区域的开头位置
        if (sel && sel.rangeCount > 0) {
            const range = sel.getRangeAt(0)       // 获取选择范围
            const atStart = range.startOffset === 0  // 判断光标是否在开头
            // 判断内容是否为空（空字符串或只有占位符）
            const isEmpty = el.innerText.trim() === '' ||
                el.innerText === block.placeholder
            // 判断是否有实际的用户输入内容
            const hasContent = block.hasUserInput && block.value.trim() !== ''

            // 第一次按backspace：如果有内容，先清空内容
            if (atStart && hasContent) {
                e.preventDefault()                  // 阻止默认删除行为
                clearHighlightBlock(currentIndex)   // 清空高亮块内容
                return
            }

            // 第二次按backspace（内容已清空）：删除整个高亮区域
            if (atStart && isEmpty) {
                e.preventDefault()                  // 阻止默认删除行为
                deleteHighlightBlock(currentIndex)  // 删除整个高亮块
                return
            }
        }
    }

    // 对于普通文本区域，使用默认的backspace行为
    // 但如果在开头且为空，可以跳转到前一个组件
    if (block.type === 'text') {
        if (sel && sel.rangeCount > 0) {
            const range = sel.getRangeAt(0)       // 获取选择范围
            const atStart = range.startOffset === 0  // 判断光标是否在开头
            const isEmpty = el.innerText.trim() === ''  // 判断内容是否为空

            if (atStart && isEmpty) {
                e.preventDefault()                  // 阻止默认删除行为
                jumpToPreviousBlock(currentIndex)   // 跳转到前一个块
                return
            }
        }
    }

    // 其他情况使用浏览器默认的backspace行为
}

/**
 * 处理delete键事件
 * 与backspace键类似，支持高亮区域的渐进式删除，但从结尾开始
 * @param {KeyboardEvent} e - 键盘事件对象
 * @param {number} currentIndex - 当前块的索引位置
 */
function handleDeleteKey(e, currentIndex) {
    const el = e.target                       // 获取当前DOM元素
    const sel = window.getSelection()         // 获取当前选择对象
    const block = blocks.value[currentIndex]  // 获取当前块数据

    // 如果是高亮区域，实现特殊的删除逻辑
    if (block.type === 'highlight') {
        // 检查是否在高亮区域的结尾位置
        if (sel && sel.rangeCount > 0) {
            const range = sel.getRangeAt(0)       // 获取选择范围
            const atEnd = range.startOffset === el.innerText.length  // 判断光标是否在结尾
            // 判断内容是否为空（空字符串或只有占位符）
            const isEmpty = el.innerText.trim() === '' ||
                el.innerText === block.placeholder
            // 判断是否有实际的用户输入内容
            const hasContent = block.hasUserInput && block.value.trim() !== ''

            // 第一次按delete：如果有内容，先清空内容
            if (atEnd && hasContent) {
                e.preventDefault()                  // 阻止默认删除行为
                clearHighlightBlock(currentIndex)   // 清空高亮块内容
                return
            }

            // 第二次按delete（内容已清空）：删除整个高亮区域
            if (atEnd && isEmpty) {
                e.preventDefault()                  // 阻止默认删除行为
                deleteHighlightBlock(currentIndex)  // 删除整个高亮块
                return
            }
        }
    }

    // 对于普通文本区域，使用默认的delete行为
    // 但如果在结尾且为空，可以跳转到下一个组件
    if (block.type === 'text') {
        if (sel && sel.rangeCount > 0) {
            const range = sel.getRangeAt(0)       // 获取选择范围
            const atEnd = range.startOffset === el.innerText.length  // 判断光标是否在结尾
            const isEmpty = el.innerText.trim() === ''  // 判断内容是否为空

            if (atEnd && isEmpty && currentIndex < blocks.value.length - 1) {
                e.preventDefault()                  // 阻止默认删除行为
                focusBlock(currentIndex + 1, 'start')  // 跳转到下一个块的开头
                return
            }
        }
    }

    // 其他情况使用浏览器默认的delete行为
}
// ==================== 高亮块操作函数 ====================
/**
 * 清空高亮区域内容
 * 将高亮块的内容清空，恢复到占位符状态，但不删除块本身
 * @param {number} index - 高亮区域在blocks数组中的索引位置
 */
function clearHighlightBlock(index) {
    const block = blocks.value[index]         // 获取指定的块数据
    if (block.type === 'highlight') {
        block.value = ''                        // 清空用户输入的值
        block.hasUserInput = false              // 标记为没有用户输入
        block.displayValue = block.placeholder  // 恢复显示占位符
    }

    emitChangeEvent()                         // 触发变化事件，更新v-model
}

/**
 * 删除整个高亮区域
 * 完全移除高亮块，并合并相邻的文本块，然后重新聚焦
 * @param {number} index - 要删除的高亮区域索引位置
 */
function deleteHighlightBlock(index) {
    const block = blocks.value[index]         // 获取指定的块数据
    if (block.type === 'highlight') {
        // 从blocks数组中删除该块
        blocks.value.splice(index, 1)

        // 同步更新DOM引用数组，保持索引一致性
        blockRefs.value.splice(index, 1)

        // 合并删除后可能相邻的文本块
        mergeAdjacentTextBlocks()

        // 等待DOM更新后，聚焦到合适的位置
        nextTick(() => {
            const targetIndex = Math.max(0, index - 1)  // 计算目标索引（不小于0）
            if (targetIndex < blocks.value.length) {
                focusBlock(targetIndex, 'end')      // 聚焦到前一个块的末尾
            }
        })

        emitChangeEvent()                       // 触发变化事件，更新v-model
    }
}

/**
 * 合并相邻的文本块
 * 当删除高亮块后，可能会产生相邻的文本块，需要将它们合并为一个块
 * 使用倒序遍历避免索引变化问题
 */
function mergeAdjacentTextBlocks() {
    // 从后往前遍历，避免删除元素时索引变化的问题
    for (let i = blocks.value.length - 1; i > 0; i--) {
        const currentBlock = blocks.value[i]     // 当前块
        const prevBlock = blocks.value[i - 1]    // 前一个块

        // 如果当前块和前一个块都是文本块，则合并它们
        if (currentBlock.type === 'text' && prevBlock.type === 'text') {
            prevBlock.value += currentBlock.value  // 将当前块的内容追加到前一个块
            blocks.value.splice(i, 1)             // 删除当前块
            blockRefs.value.splice(i, 1)          // 同步删除DOM引用
        }
    }
}

/**
 * 跳转到前一个组件块
 * 用于键盘导航时向前跳转
 * @param {number} currentIndex - 当前块的索引位置
 */
function jumpToPreviousBlock(currentIndex) {
    if (currentIndex > 0) {                   // 确保不是第一个块
        focusBlock(currentIndex - 1, 'end')     // 聚焦到前一个块的末尾
    }
}

// ==================== 容器交互处理 ====================
/**
 * 处理容器点击事件
 * 当用户点击输入框的空白区域时，聚焦到最后一个块
 * @param {MouseEvent} e - 鼠标点击事件对象
 */
function handleContainerClick(e) {
    // 检查点击的是否是容器本身（而不是子元素）
    if (e.target === e.currentTarget) {
        e.preventDefault()                      // 阻止默认行为
        const lastIndex = blocks.value.length - 1  // 计算最后一个块的索引
        if (lastIndex >= 0) {                   // 确保至少有一个块
            nextTick(() => {                      // 等待DOM更新
                focusBlock(lastIndex, 'end')        // 聚焦到最后一个块的末尾
            })
        }
    }
}

/**
 * 处理placeholder点击事件
 * 当用户点击placeholder时，聚焦到第一个可编辑区域
 */
function handlePlaceholderClick() {
    // 如果没有blocks，先初始化
    if (blocks.value.length === 0) {
        initializeComponent()
    }

    // 聚焦到第一个高亮区域，如果没有则聚焦到第一个块
    nextTick(() => {
        const firstHighlightIndex = blocks.value.findIndex(block => block.type === 'highlight')
        if (firstHighlightIndex !== -1) {
            focusBlock(firstHighlightIndex, 'start')
        } else if (blocks.value.length > 0) {
            focusBlock(0, 'start')
        }
    })
}

/**
 * 全选所有文本内容
 * 创建一个跨越所有块的选择范围，实现真正的全选功能
 */
function selectAllText() {
    if (!containerRef.value) return         // 确保容器引用存在

    const selection = window.getSelection() // 获取选择对象
    if (!selection) return                  // 确保选择对象存在

    selection.removeAllRanges()            // 清除现有的所有选择范围

    const range = document.createRange()   // 创建新的选择范围
    range.selectNodeContents(containerRef.value)  // 选择容器的所有内容
    selection.addRange(range)             // 添加选择范围到选择对象


}

/**
 * 手动插入换行符
 * 用于Ctrl+Enter和Shift+Enter的换行功能
 * @param {HTMLElement} element - 目标元素
 */
function insertLineBreak(element) {
    if (!element) return

    const selection = window.getSelection()
    if (!selection || selection.rangeCount === 0) return

    const range = selection.getRangeAt(0)

    // 创建换行符节点
    const br = document.createElement('br')

    // 删除选中的内容（如果有）
    range.deleteContents()

    // 插入换行符
    range.insertNode(br)

    // 将光标移动到换行符后面
    range.setStartAfter(br)
    range.setEndAfter(br)
    selection.removeAllRanges()
    selection.addRange(range)

    // 触发输入事件，更新组件状态
    const inputEvent = new Event('input', { bubbles: true })
    element.dispatchEvent(inputEvent)
}

/**
 * 处理复制文本操作
 * 当用户按Ctrl+C时，复制完整的文本内容到剪贴板
 */
function handleCopyText() {
    const fullText = getFullText()            // 获取完整的文本内容

    // 尝试使用现代的Clipboard API
    if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(fullText).catch(() => {
            // 如果现代API失败，回退到传统方法
            fallbackCopyText(fullText)
        })
    } else {
        // 浏览器不支持现代API，使用传统方法
        fallbackCopyText(fullText)
    }
}

/**
 * 传统的复制文本方法（兼容旧浏览器）
 * @param {string} text - 要复制的文本
 */
function fallbackCopyText(text) {
    // 创建临时的textarea元素
    const textarea = document.createElement('textarea')
    textarea.value = text                     // 设置要复制的文本
    textarea.style.position = 'fixed'        // 固定定位，避免影响布局
    textarea.style.opacity = '0'             // 透明，用户看不到
    document.body.appendChild(textarea)      // 添加到DOM

    textarea.select()                        // 选择文本
    textarea.setSelectionRange(0, 99999)     // 确保选择所有文本

    try {
        // 使用已弃用但兼容性好的execCommand作为回退方案
        // eslint-disable-next-line deprecation/deprecation
        document.execCommand('copy')  // 执行复制命令
    } catch (err) {
        // 静默处理错误，避免控制台噪音
    }

    document.body.removeChild(textarea)      // 清理临时元素
}

// ==================== 文本处理和事件发射 ====================
/**
 * 处理表单提交或其他自定义逻辑
 * 当用户在最后一个块按回车键时触发
 */
function handleFormSubmit() {
    const fullText = getFullText()             // 获取完整的文本内容

    // 构建详细的块信息
    const textBlocks = []
    const highlightBlocks = []

    blocks.value.forEach((block, index) => {
        if (block.type === 'text') {
            textBlocks.push({ index, value: block.value })
        } else {
            highlightBlocks.push({
                index,
                value: block.value,
                placeholder: block.placeholder,
                hasUserInput: block.hasUserInput
            })
        }
    })

    // 触发提交事件，父组件可以监听此事件
    emit('submit', {
        fullText,                               // 完整文本
        textBlocks,                             // 文本块数组
        highlightBlocks,                        // 高亮块数组
        totalBlocks: blocks.value.length        // 总块数
    })

    // 同时触发回车事件（向后兼容）
    emit('enter', fullText)
}

/**
 * 获取完整的文本内容
 * 根据keepBrackets属性决定是否保留中括号
 * @returns {string} 拼接后的完整文本内容
 */
function getFullText() {
    let result = ''                           // 结果字符串

    blocks.value.forEach(block => {
        if (block.type === 'text') {
            // 普通文本块：直接追加内容
            result += block.value
        } else {
            // 高亮块：根据用户输入状态选择内容
            const content = block.hasUserInput ? block.value : block.placeholder
            if (props.keepBrackets) {
                result += `[${content}]`            // 保留中括号格式
            } else {
                result += content                   // 去除中括号，只保留内容
            }
        }
    })

    return result                             // 返回拼接后的完整文本
}

/**
 * 发射内容变化事件
 * 当组件内容发生变化时，更新v-model并触发change事件
 * 这是组件与外部通信的核心函数
 */
function emitChangeEvent() {
    const fullText = getFullText()             // 获取当前完整文本

    // 发射v-model更新事件，实现双向绑定
    emit('update:modelValue', fullText)

    // 构建详细的块信息，供父组件使用
    const textBlocks = []                      // 存储所有文本块信息
    const highlightBlocks = []                 // 存储所有高亮块信息

    blocks.value.forEach((block, index) => {
        if (block.type === 'text') {
            // 收集文本块信息
            textBlocks.push({ index, value: block.value })
        } else {
            // 收集高亮块信息
            highlightBlocks.push({
                index,                              // 块在数组中的位置
                value: block.value,                 // 用户输入的值
                placeholder: block.placeholder,     // 占位符文本
                hasUserInput: block.hasUserInput    // 是否有用户输入
            })
        }
    })

    // 发射详细的change事件，包含所有必要信息
    emit('change', {
        fullText,                               // 完整的文本内容
        textBlocks,                             // 文本块数组
        highlightBlocks,                        // 高亮块数组
        totalBlocks: blocks.value.length        // 总块数
    })
}

/**
 * 确保光标在元素中可见
 * 特别处理空元素或光标在文本开头的情况
 * @param {HTMLElement} element - 目标元素
 */
function ensureCursorVisible(element) {
    if (!element) return

    try {
        const sel = window.getSelection()
        if (!sel || sel.rangeCount === 0) return

        const range = sel.getRangeAt(0)

        // 如果元素为空，确保有内容来显示光标
        if (element.textContent.length === 0) {
            // 临时插入一个零宽度空格来确保光标可见
            const textNode = document.createTextNode('\u200B')
            element.appendChild(textNode)

            // 设置光标到零宽度空格之前
            range.setStart(textNode, 0)
            range.setEnd(textNode, 0)
            sel.removeAllRanges()
            sel.addRange(range)
        } else if (range.startContainer === element && range.startOffset === 0) {
            // 如果光标在元素开头，确保光标位置正确
            const firstChild = element.firstChild
            if (firstChild && firstChild.nodeType === Node.TEXT_NODE) {
                range.setStart(firstChild, 0)
                range.setEnd(firstChild, 0)
                sel.removeAllRanges()
                sel.addRange(range)
            }
        }

        // 滚动到光标位置（如果需要）
        element.scrollIntoView({ block: 'nearest', inline: 'nearest' })
    } catch (error) {
        // 静默处理错误
    }
}

// ==================== 光标位置检测函数 ====================
/**
 * 检测光标是否在元素的开头位置
 * @param {HTMLElement} element - 目标元素
 * @param {Range} range - 选择范围
 * @returns {boolean} 是否在开头
 */
function isCaretAtStart(element, range) {
    if (!element || !range) return false

    // 检查range是否在元素的开头
    if (range.startOffset === 0) {
        // 如果元素有子节点，检查是否在第一个文本节点的开头
        if (element.childNodes.length > 0) {
            const firstNode = element.childNodes[0]
            return range.startContainer === firstNode || range.startContainer === element
        } else {
            // 空元素，检查是否在元素本身的开头
            return range.startContainer === element
        }
    }

    return false
}

/**
 * 检测光标是否在元素的结尾位置
 * @param {HTMLElement} element - 目标元素
 * @param {Range} range - 选择范围
 * @returns {boolean} 是否在结尾
 */
function isCaretAtEnd(element, range) {
    if (!element || !range) return false

    const textContent = element.textContent || ''
    const textLength = textContent.length

    // 如果元素为空，光标在元素内就是结尾
    if (textLength === 0) {
        return range.startContainer === element
    }

    // 检查光标是否在文本内容的结尾
    if (element.childNodes.length > 0) {
        const lastNode = element.childNodes[element.childNodes.length - 1]
        if (lastNode.nodeType === Node.TEXT_NODE) {
            return range.startContainer === lastNode && range.startOffset === lastNode.textContent.length
        } else {
            // 如果最后一个节点不是文本节点（比如<br>），检查光标是否在元素结尾
            return range.startContainer === element && range.startOffset === element.childNodes.length
        }
    } else {
        // 没有子节点，检查是否在元素结尾
        return range.startContainer === element && range.startOffset === textLength
    }
}

// ==================== 焦点控制函数 ====================
/**
 * 聚焦指定的组件块
 * 用于键盘导航和程序化的焦点控制
 * @param {number} index - 要聚焦的组件块索引
 * @param {string} pos - 光标位置：'start'表示开头，'end'表示结尾
 */
function focusBlock(index, pos) {
    // 使用setTimeout确保在所有事件处理完成后执行
    setTimeout(() => {
        const targetRef = blockRefs.value[index]  // 获取目标DOM元素

        if (targetRef && document.contains(targetRef)) {  // 确保元素存在且在DOM中
            targetRef.focus()                     // 设置焦点到目标元素

            // 等待焦点设置完成后再设置光标位置
            nextTick(() => {
                if (pos === 'start') {
                    setCursorToStart(targetRef)       // 将光标设置到开头
                } else {
                    setCursorToPos(targetRef, 'end')  // 将光标设置到结尾
                }
            })
        }
    }, 10) // 短暂延迟确保DOM状态稳定
}

// ==================== 组件初始化 ====================
/**
 * 初始化组件数据
 * 解析预设文本或v-model值，生成blocks数组，并保留用户已输入的内容
 * 这个函数在组件挂载和数据变化时被调用
 */
function initializeComponent() {
    // 第一步：保存当前用户输入状态，防止重新初始化时丢失用户数据
    const currentUserInputs = new Map()      // 使用Map存储用户输入状态
    if (blocks.value.length > 0) {           // 如果已有块数据
        blocks.value.forEach((block, index) => {
            // 只保存有用户输入的高亮块状态
            if (block.type === 'highlight' && block.hasUserInput) {
                currentUserInputs.set(index, {
                    value: block.value,               // 用户输入的值
                    hasUserInput: block.hasUserInput  // 用户输入状态
                })
            }
        })
    }

    // 第二步：根据props决定使用哪个文本源进行初始化
    if (!props.modelValue && props.presetText) {
        // 如果v-model为空但有预设文本，使用预设文本初始化
        emit('update:modelValue', props.presetText)  // 将预设文本赋值给v-model
        blocks.value = parsePresetText(props.presetText)  // 解析预设文本
    } else {
        // 使用v-model的值或预设文本作为数据源
        const textToUse = props.modelValue || props.presetText
        blocks.value = parsePresetText(textToUse)    // 解析文本生成块数组

        // 如果有v-model内容，需要解析其中可能包含的用户输入
        if (props.modelValue) {
            parseModelValue(props.modelValue)     // 解析v-model中的用户输入内容
        }
    }

    // 第三步：恢复之前保存的用户输入状态
    if (currentUserInputs.size > 0) {        // 如果有保存的用户输入
        blocks.value.forEach((block, index) => {
            // 恢复对应位置的高亮块的用户输入状态
            if (block.type === 'highlight' && currentUserInputs.has(index)) {
                const savedInput = currentUserInputs.get(index)
                block.value = savedInput.value      // 恢复用户输入的值
                block.hasUserInput = savedInput.hasUserInput  // 恢复输入状态
                block.displayValue = savedInput.value  // 恢复显示值
            }
        })
    }

    // 第四步：重置DOM引用数组，等待新的DOM元素挂载
    blockRefs.value = []
}

/**
 * 解析modelValue中的用户输入内容
 * 当v-model有值时，解析其中的中括号内容，识别哪些是用户输入的内容
 * @param {string} modelValue - v-model绑定的值，可能包含用户已输入的内容
 */
function parseModelValue(modelValue) {
    // 使用正则表达式匹配所有中括号及其内容
    const regex = /\[([^\]]*)\]/g            // 匹配 [内容] 格式
    let match                                // 当前匹配结果
    let highlightIndex = 0                   // 高亮块索引计数器

    // 循环查找所有中括号匹配
    while ((match = regex.exec(modelValue)) !== null) {
        // 获取当前所有高亮块
        const highlightBlocks = blocks.value.filter(block => block.type === 'highlight')
        if (highlightIndex < highlightBlocks.length) {
            const block = highlightBlocks[highlightIndex]  // 获取对应的高亮块
            const content = match[1]              // 提取中括号内的内容

            // 判断内容是否为用户输入（不等于占位符且不为空）
            if (content !== block.placeholder && content.trim() !== '') {
                block.value = content               // 设置用户输入的值
                block.hasUserInput = true           // 标记为有用户输入
                block.displayValue = content        // 更新显示值
            } else {
                // 如果内容等于占位符或为空，保持原有状态不变
                block.displayValue = block.hasUserInput ? block.value : block.placeholder
            }
        }
        highlightIndex++                        // 移动到下一个高亮块
    }
}

// ==================== 生命周期钩子 ====================
/**
 * 组件挂载后的初始化操作
 * 初始化组件数据并设置初始焦点
 */
onMounted(() => {
    // 初始化组件数据结构
    initializeComponent()

    // 等待DOM完全渲染后设置焦点
    nextTick(() => {
        // 再等待一个tick确保所有ref都设置完成
        nextTick(() => {
            focusFirstHighlight()
        })
    })
})

// ==================== 响应式监听器 ====================
/**
 * 监听modelValue变化，实现外部数据到组件的同步
 * 当父组件修改v-model值时，组件内容会相应更新
 */
watch(() => props.modelValue, (newValue) => {
    if (newValue !== undefined) {
        // 避免循环更新：只有当新值与当前生成的文本不同时才更新
        // 这样可以防止组件内部变化触发的更新造成无限循环
        const currentText = getFullText()
        if (newValue !== currentText) {
            initializeComponent()             // 重新初始化组件以反映新的值
        }
    }
}, { immediate: false })                // 不立即执行，避免初始化时的重复调用

/**
 * 监听preset-text变化，处理模板更新
 * 当预设文本变化时，智能决定是否更新组件内容
 */
watch(() => props.presetText, (newPresetText) => {
    if (newPresetText) {
        // 检查当前是否有用户输入，避免覆盖用户数据
        const hasUserInput = blocks.value.some(block =>
            block.type === 'highlight' && block.hasUserInput
        )

        if (!props.modelValue && !hasUserInput) {
            // 只有在v-model为空且没有用户输入时，才用新的预设文本更新v-model
            // 这样可以避免意外覆盖用户已输入的内容
            emit('update:modelValue', newPresetText)
        }

        // 总是重新初始化以更新模板结构，但会保留用户输入的内容
        initializeComponent()
    }
}, { immediate: false })                // 不立即执行，避免初始化时的重复调用
</script>

<style scoped>
/* ==================== 主容器样式 ==================== */
/* 高亮文本输入框的主容器，包含所有文本块和高亮块 */
.highlight-input {
    width: 100%;
    /* 占满父容器宽度 */
    min-height: 40px;
    /* 最小高度，确保有足够的点击区域 */
    /* border: 1px solid #dcdfe6; */
    /* 边框已注释，根据需要启用 */
    border-radius: 6px;
    /* 圆角边框，现代化外观 */
    padding: 8px 12px;
    /* 内边距，提供舒适的输入空间 */
    background: #fff;
    /* 白色背景 */
    font-size: 16px;
    /* 字体大小，适合阅读和输入 */
    line-height: 1.8;
    /* 行高，提供良好的文本间距 */
    cursor: text;
    /* 鼠标悬停时显示文本光标 */
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    /* 系统字体栈 */
    text-align: left;
    /* 左对齐文本 */
    word-break: break-all;
    /* 允许在任意字符间换行 */
    word-wrap: break-word;
    /* 长单词自动换行 */
    overflow-wrap: break-word;
    /* 现代浏览器的换行属性 */
    position: relative;
    /* 相对定位，为子元素提供定位上下文 */
    white-space: pre-wrap;
    /* 保留空白字符并允许换行 */
    box-sizing: border-box;
    /* 边框盒模型，padding和border包含在width内 */
}

/* 扩展点击区域的伪元素 */
.highlight-input::after {
    content: '';
    /* 空内容的伪元素 */
    flex: 1;
    /* 占据剩余空间 */
    min-height: 1px;
    /* 最小高度，确保伪元素存在 */
}

/* 容器悬停状态 - 已注释，根据需要启用 */
.highlight-input:hover {
    border-color: #c0c4cc;
    /* 悬停时的边框颜色 */
}

/* 容器内有元素聚焦时的状态 */
.highlight-input:focus-within {
    /* border-color: #409eff; */
    /* 聚焦时的边框颜色已注释 */
    box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
    /* 聚焦时的外发光效果 */
}

/* ==================== 普通文本块样式 ==================== */
/* 普通文本块的样式，用于显示和编辑常规文本内容 */
.normal-text[contenteditable] {
    outline: none;
    /* 移除聚焦时的默认轮廓 */
    color: #333;
    /* 深灰色文字，易于阅读 */
    background: transparent;
    /* 透明背景，与容器背景一致 */
    margin: 0;
    /* 移除默认外边距 */
    border: none;
    /* 移除边框 */
    white-space: pre-wrap;
    /* 保留空白字符并允许换行 */
    word-break: break-all;
    /* 允许在任意字符间换行 */
    word-wrap: break-word;
    /* 长单词自动换行 */
    overflow-wrap: break-word;
    /* 现代浏览器的换行属性 */
    hyphens: auto;
    /* 自动连字符 */
    text-align: left;
    /* 左对齐文本 */
    display: inline;
    /* 内联显示，支持文本流换行 */
    vertical-align: baseline;
    /* 基线对齐 */
    caret-color: #333;
    /* 光标颜色与文字颜色一致 */
    min-width: 1px;
    /* 最小宽度，确保空文本块也能显示光标 */
    min-height: 1.2em;
    /* 最小高度，确保有足够的光标显示空间 */
}

/* 防止空的普通文本块塌陷 */
.normal-text[contenteditable]:empty:before {
    content: '\200B';
    /* 零宽度空格，确保空元素有高度 */
}

/* 确保普通文本块聚焦时光标可见 */
.normal-text[contenteditable]:focus {
    position: relative;
    /* 相对定位，确保光标层级正确 */
    z-index: 1;
    /* 提高层级，确保光标在其他元素之上 */
}

/* 特殊处理：高亮块后的普通文本块 */
.normal-text.after-highlight[contenteditable] {
    margin-left: 2px;
    /* 添加微小间距，确保光标有显示空间 */
    position: relative;
    /* 相对定位，确保光标层级正确 */
}

/* 为高亮块后的普通文本块添加额外的光标可见性保证 */
.normal-text.after-highlight[contenteditable]:focus {
    background: rgba(64, 158, 255, 0.05);
    /* 极淡的背景色，帮助显示光标位置 */
}

/* 确保光标在文本开头时可见 */
.normal-text[contenteditable]:focus:empty:after {
    content: '';
    /* 空内容 */
    display: inline-block;
    /* 内联块显示 */
    width: 1px;
    /* 1像素宽度 */
    height: 1em;
    /* 与文字高度一致 */
    background: transparent;
    /* 透明背景 */
}

/* ==================== 整体占位符样式 ==================== */
/* 当输入框为空时显示的占位符 */
.input-placeholder {
    position: absolute;
    /* 绝对定位，覆盖在输入框上 */
    top: 8px;
    /* 与容器padding保持一致 */
    left: 12px;
    /* 与容器padding保持一致 */
    right: 12px;
    /* 与容器padding保持一致 */
    color: #999;
    /* 灰色文字，表示占位符 */
    font-size: 16px;
    /* 与输入框字体大小一致 */
    line-height: 1.8;
    /* 与输入框行高一致 */
    pointer-events: auto;
    /* 允许点击事件 */
    cursor: text;
    /* 文本光标 */
    user-select: none;
    /* 禁止选择占位符文本 */
    z-index: 1;
    /* 确保在输入内容之上 */
}

/* 输入框有内容时隐藏占位符 */
.highlight-input:not(.is-empty) .input-placeholder {
    display: none;
    /* 有内容时隐藏占位符 */
}

/* ==================== 高亮区域块样式 ==================== */
/* 高亮区域的样式，用于显示需要用户填写的特殊区域 */
.highlight-block[contenteditable] {
    outline: none;
    /* 移除聚焦时的默认轮廓 */
    min-width: 2em;
    /* 最小宽度，确保空区域可见 */
    min-height: 1.2em;
    /* 最小高度，确保有足够的点击区域 */
    margin: 0;
    /* 移除默认外边距 */
    padding: 2px 6px;
    /* 内边距，提供舒适的输入空间 */
    border-radius: 4px;
    /* 圆角，现代化外观 */
    background: #f0f9ff;
    /* 浅蓝色背景，突出显示 */
    color: #409eff;
    /* 蓝色文字，与背景协调 */
    font-weight: 500;
    /* 中等字重，增强可读性 */
    /* box-shadow: 0 0 0 1px #b3d8ff inset; */
    /* 内阴影已注释，根据需要启用 */
    position: relative;
    /* 相对定位，为伪元素提供上下文 */
    border: none;
    /* 移除边框 */
    white-space: pre-wrap;
    /* 保留空白字符并允许换行 */
    word-break: break-all;
    /* 允许在任意字符间换行 */
    word-wrap: break-word;
    /* 长单词自动换行 */
    overflow-wrap: break-word;
    /* 现代浏览器的换行属性 */
    hyphens: auto;
    /* 自动连字符 */
    text-align: left;
    /* 左对齐文本 */
    display: inline-block;
    /* 内联块显示，支持设置宽高 */
    vertical-align: top;
    /* 顶部对齐，与文本基线对齐 */
    caret-color: #409eff;
    /* 光标颜色与文字颜色一致 */
    max-width: 100%;
    /* 最大宽度不超过容器 */
    box-sizing: border-box;
    /* 边框盒模型 */
}

/* 高亮区域聚焦状态 */
.highlight-block[contenteditable]:focus {
    background: #e6f7ff;
    /* 聚焦时的背景色，比默认稍深 */
    /* box-shadow: 0 0 0 2px #409eff inset; */
    /* 内阴影已注释 */
    /* border-color: #409eff; */
    /* 边框颜色已注释 */
}

/* ==================== 用户输入状态样式 ==================== */
/* 当高亮区域有用户输入内容时的样式（文字加深效果） */
.highlight-block.has-content {
    color: #1760a0;
    /* 更深的蓝色，突出用户输入 */
    font-weight: 700;
    /* 加粗字体，增强视觉效果 */
    background: #f0f9ff;
    /* 保持背景色一致 */
}

/* 有用户输入内容的高亮区域聚焦时的样式 */
.highlight-block.has-content:focus {
    color: #1760a0;
    /* 保持深蓝色 */
    background: #e6f7ff;
    /* 聚焦时的背景色 */
}

/* ==================== 占位符样式 ==================== */
/* 高亮区域为空时显示占位符文本 */
.highlight-block[contenteditable]:empty:before {
    content: attr(data-placeholder);
    /* 从data-placeholder属性获取占位符文本 */
    /* font-weight: 400; */
    /* 字重已注释，使用默认 */
    pointer-events: none;
    /* 禁用鼠标事件，避免干扰输入 */
}

/* 聚焦时占位符的透明度效果 */
.highlight-block[contenteditable]:focus:empty:before {
    opacity: 0.3;
    /* 聚焦时占位符变淡，提示用户可以输入 */
}

/* ==================== 布局优化 ==================== */
/* 防止文本块不必要的收缩 */
.highlight-input>span {
    flex-shrink: 0;
    /* 禁止收缩，保持原始大小 */
}

/* 最后一个元素的特殊处理 */
.highlight-input>span:last-child {
    flex-shrink: 1;
    /* 允许最后一个元素收缩 */
    min-width: auto;
    /* 移除最小宽度限制 */
}

/* ==================== 响应式设计 ==================== */
/* 移动端适配 */
@media (max-width: 768px) {
    .highlight-input {
        font-size: 14px;
        /* 移动端使用较小字体 */
        padding: 6px 10px;
        /* 减少内边距，节省空间 */
    }

    /* 移动端高亮区域样式调整 */
    .highlight-block[contenteditable] {
        padding: 1px 4px;
        /* 减少内边距，适应小屏幕 */
        min-width: 1.5em;
        /* 减少最小宽度 */
    }

    /* 移动端允许更灵活的换行 */
    .highlight-input>span {
        flex-shrink: 1;
        /* 允许所有元素收缩，提高空间利用率 */
    }
}

/* ==================== 无障碍支持 ==================== */
/* 为聚焦的高亮区域提供无障碍轮廓 */
.highlight-block[contenteditable]:focus {
    outline: 2px solid transparent;
    /* 透明轮廓，不影响视觉但支持屏幕阅读器 */
    outline-offset: 2px;
    /* 轮廓偏移，避免与内容重叠 */
}

/* ==================== 高对比度模式支持 ==================== */
/* 为视觉障碍用户提供更好的对比度 */
@media (prefers-contrast: high) {
    .highlight-block[contenteditable] {

        border: 2px solid #409eff;
        /* 高对比度模式下添加明显边框 */
        background: #ffffff;
        /* 纯白背景，提高对比度 */
    }

    /* 有用户输入内容时的高对比度样式 */
    .highlight-block.has-content {
        background: #f0f9ff;
        /* 浅色背景，保持可读性 */
        border-color: #1760a0;
        /* 深色边框，增强对比 */
    }
}
</style>